package cn.miju.ser.framework.request;

import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.miju.common.content.CodeMessage;
import cn.miju.common.util.ValidationUtil;
import cn.miju.database.constant.DbConstant;
import cn.miju.database.entity.Merchant;
import cn.miju.ser.framework.annotation.DirectRequest;
import cn.miju.ser.framework.constant.SerConstant;
import cn.miju.ser.framework.exception.ApiException;
import cn.miju.ser.framework.handler.MiJuContext;
import cn.miju.ser.framework.handler.MiJuContextHandler;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.Objects;

/**
 * <pre>
 * 业务类
 * </pre>
 *
 * @author zhuming
 * @version 1.0.0
 * @since 2023-03-07 9:29
 */
@Slf4j
@RequiredArgsConstructor
@RestControllerAdvice(basePackages = {"cn.miju"})
public class RequestAdvice implements RequestBodyAdvice {

    private final StringRedisTemplate redisTemplate;
    @Value("${miju.verify-sign:true}")
    private boolean hasSign;
    @Value("${spring.profiles.active}")
    private String profilesActive;
    @Value("${miju.test-merchant-id:}")
    private Long testMerchantId;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return !methodParameter.hasMethodAnnotation(DirectRequest.class);
    }

    @NotNull
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        //非开发环境统一接口规范
        if (!Objects.equals(profilesActive, "dev")) {
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    String bodyStr = IOUtils.toString(inputMessage.getBody(), StandardCharsets.UTF_8);
                    MiJuPayRequest miJuPayRequest = JSON.parseObject(bodyStr, MiJuPayRequest.class);
                    if (!ValidationUtil.toValidate(miJuPayRequest)) {
                        throw new ApiException("未按照统一接口规范传参！");
                    }
                    // 验签，防篡改
                    if (hasSign) {
                        String sign = miJuPayRequest.getSign();
                        JSONObject bodyJson = (JSONObject) JSON.toJSON(miJuPayRequest);
                        bodyJson.remove("sign");
                        String signResult = SecureUtil.md5(bodyJson.toString());
                        if (!Objects.equals(sign, signResult)) {
                            throw new ApiException(CodeMessage.ILLEGAL_REQUEST.getCode(), CodeMessage.ILLEGAL_REQUEST.getMessage() + "验签失败");
                        }
                        //基于nonce + timestamp校验，防复用（重复请求）
                        String nonceKey = SerConstant.Redis.NONCE + miJuPayRequest.getTransSerialNo();
                        if (Boolean.TRUE.equals(redisTemplate.hasKey(nonceKey))) {
                            throw new ApiException(CodeMessage.ILLEGAL_REQUEST.getCode(), CodeMessage.ILLEGAL_REQUEST.getMessage() + "重复请求");
                        }
                        LocalDateTime requestTime = LocalDateTime.parse(miJuPayRequest.getTimeStamp(), DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
                        redisTemplate.opsForValue().set(nonceKey, miJuPayRequest.getTimeStamp(), Duration.between(requestTime, requestTime.plusSeconds(60)));
                    }

                    // 鉴权
                    String merchantInfoKey = SerConstant.Redis.MERCHANT_INFO + miJuPayRequest.getPlatformMerchantNo();
                    if (Boolean.FALSE.equals(redisTemplate.hasKey(merchantInfoKey))) {
                        throw new ApiException(CodeMessage.ILLEGAL_REQUEST, "平台商户号不存在");
                    }
                    Merchant merchant = JSONObject.parseObject(redisTemplate.opsForValue().get(merchantInfoKey), Merchant.class);
                    if (Objects.isNull(merchant)) {
                        throw new ApiException(String.format("商户缓存[key：%s]信息读取为空！", merchantInfoKey));
                    }
                    if (DbConstant.MerchantStatus.DISABLE.equals(merchant.getStatus())) {
                        throw new ApiException(CodeMessage.ILLEGAL_REQUEST, "商户未启用/已停用");
                    }
                    if (Objects.isNull(miJuPayRequest.getBizContent())) {
                        throw new ApiException(CodeMessage.VALIDATE_FAIL, "请求参数为空");
                    }
                    String paramJson = miJuPayRequest.getBizContent();
                    // 解密
                    if (Boolean.TRUE.equals(miJuPayRequest.getEncrypt())) {
                        byte[] decrypt;
                        try {
                            // 私钥解密
                            RSA rsa = new RSA(merchant.getPrivateKey(), null);
                            decrypt = rsa.decrypt(miJuPayRequest.getBizContent(), KeyType.PrivateKey);
                        } catch (Exception e) {
                            throw new ApiException(CodeMessage.DECRYPT_FAIL);
                        }
                        paramJson = new String(decrypt, StandardCharsets.UTF_8);
                    }
                    MiJuContextHandler.setMiJuContext(MiJuContext.builder().merchantId(merchant.getId()).toMerchantTradeNo(merchant.isToMerchantTradeNo()).encryptResponseBody(miJuPayRequest.getEncrypt()).build());
                    return IOUtils.toInputStream(paramJson, StandardCharsets.UTF_8);
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        }
        if (Objects.isNull(testMerchantId) || testMerchantId == 0) {
            throw new ApiException("测试商户ID未配置或无效！");
        }
        MiJuContextHandler.setMiJuContext(MiJuContext.builder().merchantId(testMerchantId).toMerchantTradeNo(false).encryptResponseBody(false).build());
        return inputMessage;
    }

    @NotNull
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}
